﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using NMF.Optimizations.Linq;
using NMF.Tests;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace NMF.Optimizations.Tests.UnitTests
{
    [TestClass]
    public class OptimizationExtensionTest
    {
        private ObservableSource<string> source = new ObservableSource<string>();
        private Func<int, IObservableSource<string>> sourceFunc;
        private TestObserver<string> observer = new TestObserver<string>();

        private bool funcCalled;

        [TestInitialize]
        public void TestInit()
        {
            sourceFunc = i => source;
        }

        [TestMethod]
        public void Optimizations_OptimizationExtensions_Where1()
        {
            bool filterResult = true;
            var result = source.Where(s => { funcCalled = true; return filterResult; });
            result.Subscribe(observer);

            result.AssertEmpty();
            Assert.IsFalse(funcCalled);

            AddSourceItem("a", item => Assert.AreEqual("a", item));
            result.AssertContainsOnly("a");
            AddSourceItem("b", item => Assert.AreEqual("b", item));
            result.AssertContainsOnly("a", "b");
            UpdateItem("a", item => Assert.AreEqual("a", item));

            filterResult = false;
            Reset();
            observer.OnNewItem = item => Assert.AreEqual("c", item);
            source.Publish("c");

            Assert.IsTrue(funcCalled);
            Assert.IsFalse(observer.NewItemCalled);
            Assert.IsFalse(observer.UpdatedItemCalled);
            Assert.IsFalse(observer.DeletedItemCalled);

            Reset();
            observer.OnDeletedItem = item => Assert.AreEqual("a", item);
            source.Refresh("a");

            Assert.IsTrue(funcCalled);
            Assert.IsFalse(observer.NewItemCalled);
            Assert.IsFalse(observer.UpdatedItemCalled);
            Assert.IsTrue(observer.DeletedItemCalled);

            filterResult = true;

            RemoveSourceItem("b", item => Assert.AreEqual("b", item));
            result.AssertEmpty();
            UnsubscribeAndTest(result);
        }

        private void RemoveSourceItem(string element, Action<string> assertion)
        {
            Reset();

            observer.OnDeletedItem = assertion;
            source.Revoke(element);

            Assert.IsFalse(observer.NewItemCalled);
            Assert.IsFalse(observer.UpdatedItemCalled);
            Assert.IsTrue(observer.DeletedItemCalled);
        }

        private void Reset()
        {
            observer.Reset();
            funcCalled = false;
        }

        private void UpdateItem(string element, Action<string> assertion)
        {
            Reset();

            observer.OnUpdatedItem = assertion;
            source.Refresh(element);

            Assert.IsTrue(funcCalled);
            Assert.IsFalse(observer.NewItemCalled);
            Assert.IsTrue(observer.UpdatedItemCalled);
            Assert.IsFalse(observer.DeletedItemCalled);
        }

        private void AddSourceItem(string element, Action<string> assertion)
        {
            Reset();

            observer.OnNewItem = assertion;
            source.Publish(element);

            Assert.IsTrue(funcCalled);
            Assert.IsTrue(observer.NewItemCalled);
            Assert.IsFalse(observer.UpdatedItemCalled);
            Assert.IsFalse(observer.DeletedItemCalled);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_Where_Exception1()
        {
            var result = source.Where(null);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_Where_Exception2()
        {
            var result = (null as IObservableSource<string>).Where(s => true);
        }

        [TestMethod]
        public void Optimizations_OptimizationExtensions_Select1()
        {
            var select = source.Select(s => { funcCalled = true; return s; });
            select.Subscribe(observer);

            select.AssertEmpty();

            AddSourceItem("a", item => Assert.AreEqual("a", item));
            select.AssertContainsOnly("a");
            AddSourceItem("b", item => Assert.AreEqual("b", item));
            select.AssertContainsOnly("a", "b");
            UpdateItem("a", item => Assert.AreEqual("a", item));
            select.AssertContainsOnly("a", "b");
            RemoveSourceItem("b", item => Assert.AreEqual("b", item));
            select.AssertContainsOnly("a");
            UnsubscribeAndTest(select);
        }

        private void UnsubscribeAndTest(IObservableSource<string> result)
        {
            Reset();
            source.Reset();
            Assert.IsTrue(observer.ResetCalled);
            Reset();
            result.Unsubscribe(o => o == observer);
            source.Publish("a");
            Assert.IsFalse(observer.NewItemCalled);
            Assert.IsFalse(observer.UpdatedItemCalled);
            Assert.IsFalse(observer.DeletedItemCalled);
            Assert.IsFalse(observer.ResetCalled);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_Select_Exception1()
        {
            var result = source.Select(null as Func<string, GenericParameterHelper>);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_Select_Exception2()
        {
            var result = (null as IObservableSource<string>).Select(s => new GenericParameterHelper());
        }

        [TestMethod]
        public void Optimizations_OptimizationExtensions_SelectMany1()
        {
            var result = source.SelectMany(s => source, (s1, s2) => { funcCalled = true; return s1 + s2; });
            result.Subscribe(observer);

            result.AssertEmpty();
            AddSourceItem("a", item => Assert.AreEqual("aa", item));
            result.AssertContainsOnly("aa");
            AddSourceItem("b", item => Assert.IsTrue(item.Contains("b")));
            result.AssertContainsOnly("aa", "ab", "ba", "bb");
            UpdateItem("a", item => Assert.IsTrue(item.Contains("a")));
            result.AssertContainsOnly("aa", "ab", "ba", "bb");

            var removed = new List<string>();
            RemoveSourceItem("b", item => removed.Add(item));
            removed.AssertContainsOnly("ab", "ba", "bb");

            UnsubscribeAndTest(result);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_SelectMany_Exception1()
        {
            var result = source.SelectMany(s => source, null as Func<string, string, GenericParameterHelper>);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_SelectMany_Exception2()
        {
            var result = source.SelectMany(null as Func<string, IObservableSource<GenericParameterHelper>>, (s, g) => new GenericParameterHelper());
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_SelectMany_Exception3()
        {
            var result = (null as IObservableSource<GenericParameterHelper>).SelectMany(s => source, (g, s) => new GenericParameterHelper());
        }

        [TestMethod]
        public void Optimizations_OptimizationExtensions_SelectMany2()
        {
            var list = new List<string>() { "c", "d" };
            var result = source.SelectMany(s => list, (s1, s2) => { funcCalled = true; return s1 + s2; });
            result.Subscribe(observer);

            result.AssertEmpty();
            AddSourceItem("a", item => Assert.IsTrue(item == "ac" || item == "ad"));
            result.AssertContainsOnly("ac", "ad");
            AddSourceItem("b", item => Assert.IsTrue(item == "bc" || item == "bd"));
            result.AssertContainsOnly("ac", "ad", "bc", "bd");
            UpdateItem("a", item => Assert.IsTrue(item.Contains("a")));
            result.AssertContainsOnly("ac", "ad", "bc", "bd");

            var removed = new List<string>();
            RemoveSourceItem("b", item => removed.Add(item));
            removed.AssertContainsOnly("bc", "bd");

            UnsubscribeAndTest(result);
        }

        [TestMethod]
        public void Optimizations_OptimizationExtensions_SelectMany3()
        {
            var list = new ObservableCollection<string>() { "c", "d" };
            var result = source.SelectMany(s => list, (s1, s2) => { funcCalled = true; return s1 + s2; });
            result.Subscribe(observer);

            result.AssertEmpty();
            AddSourceItem("a", item => Assert.IsTrue(item == "ac" || item == "ad"));
            result.AssertContainsOnly("ac", "ad");
            AddSourceItem("b", item => Assert.IsTrue(item == "bc" || item == "bd"));
            result.AssertContainsOnly("ac", "ad", "bc", "bd");

            Reset();
            var added = new List<string>();
            observer.OnNewItem = item => added.Add(item);
            list.Add("e");

            added.AssertContainsOnly("ae", "be");
            result.AssertContainsOnly("ac", "ad", "ae", "bc", "bd", "be");

            observer.OnDeletedItem = item => added.Remove(item);
            list.Remove("e");

            added.AssertEmpty();
            result.AssertContainsOnly("ac", "ad", "bc", "bd");

            UpdateItem("a", item => Assert.IsTrue(item.Contains("a")));
            result.AssertContainsOnly("ac", "ad", "bc", "bd");

            var removed = new List<string>();
            RemoveSourceItem("b", item => removed.Add(item));
            removed.AssertContainsOnly("bc", "bd");

            Reset();
            list.Clear();
            Assert.IsTrue(observer.ResetCalled);

            UnsubscribeAndTest(result);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_SelectMany_Exception4()
        {
            var result = source.SelectMany(s => new List<string>() { "d" }, null as Func<string, string, GenericParameterHelper>);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_SelectMany_Exception5()
        {
            var result = source.SelectMany(null as Func<string, IEnumerable<GenericParameterHelper>>, (s, g) => new GenericParameterHelper());
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void Optimizations_OptimizationExtensions_SelectMany_Exception6()
        {
            var result = (null as IObservableSource<GenericParameterHelper>).SelectMany(s => new List<string>() { "d" }, (g, s) => new GenericParameterHelper());
        }
    }
}
